package nio.server;

import org.omg.PortableInterceptor.USER_EXCEPTION;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
 * Created by ASUS on 2017/10/19.
 *
 * 注意:
 *  通过测试:
 *     1:只要同一个客户端 发送的消息,服务端就有一个SelectionKey_OP_ACCEPT始终与其对应：
 *         一个客户端一个行为OP_READ 对应一个SelectionKey
 *
 *     2:服务端只有一个SelectionKey 处理服务大厅的接待 针对一个行为:OP_ACCEPT!
 *
 *     3:而针对每个 客户端始终接受消息而言: 对应有一唯一的 OP_READ 的SelectionKey
 *
 *     4:针对每个客户端的通道都对应的一个!SocketChannel 对象
 *
 *     5:一个通道对应的SelectionKey 通过对应的通道注册好的:比如ServerSocketChannel 开始往Selector注册一个OP_ACCEPT行为的SelectionKey,
 *        以后不需要进行再设置该行为了! 因为它专门对应负责接待客户端
 *
 *       以及对应的SocketChannel中的SelectionKey中注册一个OP_READ行为的SelectionKey,以后就不需要再重新设置interestOp行为操作了!
 *
 *     问题:
 *         问题是客户端有很多了，那么其中的SelectionKey和SocketChannel 就只有对应的一个吗?
 */
public class NioServer {
    //端口
    private int port = 8080;
    private Charset charset=Charset.forName("UTF-8");
    //用来记录在线人数,以及昵称
    private static HashSet<String> users = new HashSet<String>();

    private static String USER_EXIST = "系统提示:该昵称已经存在,请换一个昵称";
    private static String USER_CONTENT_SPILIT="#@#";
    //选择器
    private Selector selector;



    public static void main(String[] args) {
        try {
            new NioServer(8080).listener();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    //构造函数
    public NioServer(int port) throws Exception {
        this.port = port;
        //进行打开通道
        ServerSocketChannel server = ServerSocketChannel.open();
        //通道绑定端口
        server.bind(new InetSocketAddress(this.port));
        //对通道配置是否阻塞
        server.configureBlocking(false);

        //服务大厅开始开启
        selector=Selector.open();

        //通过服务端注册 选择器: 开始告诉服务大厅 你可以接客了!
        server.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("服务已经启动,监听的端口是:"+this.port);
    }

    /**
     * 开始监听
     */
    public void listener() throws IOException {

        //死循环,但是不会被阻塞
        //CPU 工作频率可控了,是可控的固定值
        while(true){
            //在轮询,我们服务大厅中,到底有多少人在排队
            int wait=selector.select();
            if(wait==0) continue; //如果没有人排队，进入下一次轮询
            //而通过这个方法 知道 select-key set 集合，取号,默认给他分配号码(排队号码)
            Set<SelectionKey> keySet=selector.selectedKeys();
            //需要遍历该集合
            Iterator<SelectionKey> iteratorKey = keySet.iterator();
            //进行遍历该集合
            while(iteratorKey.hasNext()){
                SelectionKey selectionKey=iteratorKey.next();
                //进行处理该key  处理逻辑
                processKey(selectionKey);
                //处理一个,号码就要被删除,打发他走人(别再服务大厅占着茅坑不拉屎)
                iteratorKey.remove();
            }
        }
    }

    /**
     * 进行处理该key
     * @param selectionKey
     */
    private void processKey(SelectionKey selectionKey) throws IOException {
        System.out.println("processKey selectionKey:"+selectionKey);
        //判断selectKey的状态:
        if(selectionKey.isAcceptable()){//判断客户端确定已经进入服务大厅并且已经连接好了
            //是否可以接受
            ServerSocketChannel sever = (ServerSocketChannel) selectionKey.channel();
            SocketChannel client= sever.accept();
            System.out.println("processKey isAcceptable client:"+client+"..hashcode:"+client.hashCode());
            //设置为非阻塞模式
            client.configureBlocking(false);
            //注册选择器，并且设置为读取的模式,收到一个连接请求,然后起一个SocketChannel，并注册到selector上
            client.register(selector,SelectionKey.OP_READ);
            //将次对应的channel 设置为准备接受其他客户的请求
//            selectionKey.interestOps(SelectionKey.OP_ACCEPT);
            //向客户端写入数据
            client.write(charset.encode("请输入你的昵称:"));
        }

        //处理来自客户端的数据读取的请求
        if(selectionKey.isReadable()){//判断 是否可读
                //返回selectionKey对应的channel
                SocketChannel client=(SocketChannel) selectionKey.channel();
                System.out.println("processKey isReadable client:"+client+"..hashcode:"+client.hashCode());
                //分配字节缓冲区
                ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
                StringBuilder content=new StringBuilder();
                try {
                    while(client.read(byteBuffer)>0){
                        byteBuffer.flip();//设置limit到Position中
                        content.append(charset.decode(byteBuffer));
                    }
                    //将次对应的channel设置为准备下一个次接受数据
//                    selectionKey.interestOps(SelectionKey.OP_READ);
                } catch (IOException e) {
                    e.printStackTrace();
                    //取号器进行取消
                    selectionKey.cancel();
                    if(selectionKey.channel()!=null){
                        selectionKey.channel().close();
                    }
                }

                if(content.length()>0){
                    //如果有对应的内容
                    String[] arrayContent=content.toString().split(USER_CONTENT_SPILIT);
                    //注册用户
                    if(arrayContent!=null && arrayContent.length==1){
                        String nickName=arrayContent[0];
                        if(users.contains(nickName)){
                            //如果有对应的昵称 ，返回给客户端 昵称已经注册
                            client.write(charset.encode(USER_EXIST));
                        }else{
                            //如果的话 加入昵称
                            users.add(nickName);
                            int onLineCount=onlineCount();
                            String message="欢迎"+nickName+" 进入聊天室!当前在线人数:"+onLineCount;
                            broadCast(null,message);
                        }
                    }else if(arrayContent!=null && arrayContent.length>1){//注册完了，发送消息
                        String nickName=arrayContent[0];
                        String message=nickName+"说:"+content.substring(nickName.length()+USER_CONTENT_SPILIT.length());
                        if(users.contains(nickName)){
                            //不会发给发送此内容的客户端
                            broadCast(client,message);
                        }
                    }
                }
            }
    }

    /**
     * 进行广播通知其他人
     * @param o
     * @param message
     */
    private void broadCast(Object o, String message) throws IOException {
        //主要是遍历服务大厅中的selectionKey
        for(SelectionKey key:selector.keys()){
            Channel targetChannle=key.channel();
            if (targetChannle instanceof SocketChannel && targetChannle!=o) {
                SocketChannel target=(SocketChannel)targetChannle;
                target.write(charset.encode(message));
            }
        }
    }

    /**
     * 注意:
     *     selection中的keys函数---拿出所有人的函数
     *     而其中的selectionKeys是取出正在排队的元素集合
     * 湖区对应的在线人数
     * @return
     */
    private int onlineCount() {
        int res=0;
        for(SelectionKey key:selector.keys()){
            Channel target=key.channel();
            if(target instanceof SocketChannel){
                res++;
            }
        }
        return  res;
    }
}
